package com.zxl.grocery.microservice.zuulgateway.server.filter;

import com.alibaba.fastjson.JSON;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.zxl.grocery.microservice.api.authority.PermissionInfo;
import com.zxl.grocery.microservice.api.log.LogInfo;
import com.zxl.grocery.microservice.auth.client.config.ServiceAuthConfig;
import com.zxl.grocery.microservice.auth.client.config.UserAuthConfig;
import com.zxl.grocery.microservice.auth.client.jwt.ServiceAuthUtil;
import com.zxl.grocery.microservice.auth.client.jwt.UserAuthUtil;
import com.zxl.grocery.microservice.auth.common.util.jwt.IJWTInfo;
import com.zxl.grocery.microservice.common.context.BaseContextHandler;
import com.zxl.grocery.microservice.common.msg.auth.TokenErrorResponse;
import com.zxl.grocery.microservice.common.msg.auth.TokenForbiddenResponse;
import com.zxl.grocery.microservice.common.util.ClientUtil;
import com.zxl.grocery.microservice.zuulgateway.server.feign.ILogService;
import com.zxl.grocery.microservice.zuulgateway.server.feign.IUserService;
import com.zxl.grocery.microservice.zuulgateway.server.utils.DBLog;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.http.HttpServletRequest;
import java.net.URLEncoder;
import java.util.Date;
import java.util.List;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 网关拦截器
 *
 * @author zxl
 * @create 2017-09-23 8:25
 */
@Component
@Slf4j
public class AdminAccessFilter extends ZuulFilter {

    @Autowired
    @Lazy
    private IUserService userService;
    @Autowired
    @Lazy
    private ILogService logService;

    @Value("${gate.ignore.startWith}")
    private String startWith;

    @Value("${zuul.prefix}")
    private String zuulPrefix;
    @Autowired
    private UserAuthUtil userAuthUtil;

    @Autowired
    private ServiceAuthConfig serviceAuthConfig;

    @Autowired
    private UserAuthConfig userAuthConfig;

    @Autowired
    private ServiceAuthUtil serviceAuthUtil;

//    @Autowired
//    private EurekaClient discoveryClient;
//    @PostConstruct
//    public void init() {
//        InstanceInfo prodSvcInfo = discoveryClient.getNextServerFromEureka("GROCERY-ADMIN", false);
//        ServiceFeignInterceptor serviceFeignInterceptor = new ServiceFeignInterceptor();
//        serviceFeignInterceptor.setServiceAuthConfig(serviceAuthConfig);
//        serviceFeignInterceptor.setServiceAuthUtil(serviceAuthUtil);
//        serviceFeignInterceptor.setUserAuthConfig(userAuthConfig);
//        this.userService = Feign.builder().encoder(new JacksonEncoder())
//                .decoder(new JacksonDecoder())
//                .requestInterceptor(serviceFeignInterceptor)
//                .target(IUserService.class, prodSvcInfo.getHomePageUrl());
//    }

    @Override
    public String filterType() {
//         pre：路由之前
//         routing：路由之时
//         post： 路由之后
//         error：发送错误调用
        return "pre";
    }

    @Override
    public int filterOrder() {
        return 1;//执行顺序，数字越大越靠后执行
    }

    @Override
    public boolean shouldFilter() {
        return true;//是否执行该过滤器 true执行，false不执行
    }

    @Override
    public Object run() {
        //做校验
        RequestContext ctx = RequestContext.getCurrentContext(); //获取网关上下文
        HttpServletRequest request = ctx.getRequest(); //获取当前请求
        final String requestUri = request.getRequestURI().substring(zuulPrefix.length());
        final String method = request.getMethod();
        BaseContextHandler.setToken(null);
        // 不进行拦截的地址
        if (isStartWith(requestUri)) {
            return null;
        }
        IJWTInfo user = null;
        try {
            user = getJWTUser(request, ctx);//验证用户合法性
        } catch (Exception e) {
            setFailedRequest(JSON.toJSONString(new TokenErrorResponse(e.getMessage())), 200);
            return null;
        }

        List<PermissionInfo> permissionIfs = userService.getAllPermissionInfo();//获取所有被权限管理起来的uri
        // 判断资源是否启用权限约束
//        Collection<PermissionInfo> result = getPermissionInfos(requestUri, method, permissionInfos);
//        if(result.size()>0){//如果是被管控的
//            //checkAllow(requestUri, method, ctx, user.getUniqueName());//根据当前用的唯一标识去校验该用户是否有权限
//            checkUserPermission(requestUri, method, ctx, user);
        Stream<PermissionInfo> stream = getPermissionIfs(requestUri, method, permissionIfs);
        List<PermissionInfo> result = stream.collect(Collectors.toList());
        PermissionInfo[] permissions = result.toArray(new PermissionInfo[]{});
        if (permissions.length > 0) {
            checkUserPermission(permissions, ctx, user);
        }

        // 申请客户端密钥头，放入请求头中
        ctx.addZuulRequestHeader(serviceAuthConfig.getTokenHeader(), serviceAuthUtil.getClientToken());
        BaseContextHandler.remove();

        return null;
    }

    /**
     * 获取目标权限资源  后期改为jdk8的Stream多线程并行流进行数据处理
     * 判断uri 所请求的路径属于权限管控的 或者以方式开头的并且请求方法是一致的
     * @param requestUri
     * @param method
     * @param serviceInfo
     * @return
     */
//    private Collection<PermissionInfo> getPermissionInfos(final String requestUri, final String method, List<PermissionInfo> serviceInfo) {
//        return Collections2.filter(serviceInfo, new Predicate<PermissionInfo>() {
//                @Override
//                public boolean apply(PermissionInfo permissionInfo) {
//                    String url = permissionInfo.getUri();
//                    String uri = url.replaceAll("\\{\\*\\}", "[a-zA-Z\\\\d]+");
//                    String regEx = "^" + uri + "$";
//                    return (Pattern.compile(regEx).matcher(requestUri).find() || requestUri.startsWith(url + "/"))
//                            && method.equals(permissionInfo.getMethod());
//                }
//            });
    private Stream<PermissionInfo> getPermissionIfs(final String requestUri, final String method, List<PermissionInfo> serviceInfo) {
        return serviceInfo.parallelStream().filter(new Predicate<PermissionInfo>() {
            @Override
            public boolean test(PermissionInfo permissionInfo) {
                String url = permissionInfo.getUri();
                String uri = url.replaceAll("\\{\\*\\}", "[a-zA-Z\\\\d]+");
                String regEx = "^" + uri + "$";
                return (Pattern.compile(regEx).matcher(requestUri).find() || requestUri.startsWith(url + "/"))
                        && method.equals(permissionInfo.getMethod());
            }
        });
    }

    /**
     * @Author: Lzx
     * @Description: 请求grocery-admin记录日志
     * @Params:  * @param null
     * @Date: 16:04 2017/10/31
     */
//    private void setCurrentUserInfoAndLog(RequestContext ctx, String username, PermissionInfo pm) {
//        UserInfo info = userService.getUserByUsername(username);//通过用户名获取用户基本信息
    private void setCurrentUserInfoAndLog(RequestContext ctx, IJWTInfo user, PermissionInfo pm) {
        String host = ClientUtil.getClientIp(ctx.getRequest());// 获取客户端真实ip
        ctx.addZuulRequestHeader("userId", user.getId());
        ctx.addZuulRequestHeader("userName", URLEncoder.encode(user.getName()));
        ctx.addZuulRequestHeader("userHost", host);
        LogInfo logInfo = new LogInfo(pm.getMenu(), pm.getName(), pm.getUri(), new Date(), user.getId(), user.getName(), host);
        DBLog.getInstance().setLogService(logService).offerQueue(logInfo);//写入日志
    }

    /**
     * 返回token中存放的信息
     * @param request
     * @param ctx
     * @return
     */
    private IJWTInfo getJWTUser(HttpServletRequest request, RequestContext ctx) throws Exception {
        String authToken = request.getHeader(userAuthConfig.getTokenHeader());//从Header中获取token
        if (StringUtils.isBlank(authToken)) {
            authToken = request.getParameter("token");//从请求参数中获取token
        }
        ctx.addZuulRequestHeader(userAuthConfig.getTokenHeader(), authToken);//将token放入RequestContext中
        BaseContextHandler.setToken(authToken);//将token放入当前请求的上下文中
        return userAuthUtil.getInfoFromToken(authToken);
    }

    /**
     * 读取权限
     * @param request
     * @param username
     * @return
     */
//    private List<PermissionInfo> getPermissionInfos(HttpServletRequest request, String username) {
//        List<PermissionInfo> permissionInfos;
//        if (request.getSession().getAttribute("permission") == null) {
//            permissionInfos = userService.getPermissionByUsername(username);//请求grocery-admin服务，获取用户的所有权限
//            request.getSession().setAttribute("permission", permissionInfos);//将权限列表存入session中
//        } else {
//            permissionInfos = (List<PermissionInfo>) request.getSession().getAttribute("permission");//从session中取出权限列表
//        }
//        return permissionInfos;
//    }

    /**
     * 权限校验
     * @param permissions
     * @param ctx
     * @param user
     */
////    private void checkAllow(final String requestUri, final String method , RequestContext ctx, String username) {
//    private void checkUserPermission(final String requestUri, final String method ,RequestContext ctx,IJWTInfo user) {
//            log.debug("uri：" + requestUri + "----method：" + method);
//            List<PermissionInfo> permissionInfos = getPermissionInfos(ctx.getRequest(), user.getUniqueName()) ;
//            Collection<PermissionInfo> result = getPermissionInfos(requestUri, method, permissionInfos);
//            if (result.size() <= 0) {
    private void checkUserPermission(PermissionInfo[] permissions, RequestContext ctx, IJWTInfo user) {
        List<PermissionInfo> permissionInfos = userService.getPermissionByUsername(user.getUniqueName());
        PermissionInfo current = null;
        for (PermissionInfo info : permissions) {
            boolean anyMatch = permissionInfos.parallelStream().anyMatch(new Predicate<PermissionInfo>() {
                @Override
                public boolean test(PermissionInfo permissionInfo) {
                    return permissionInfo.getCode().equals(info.getCode());
                }
            });
            if (anyMatch) {
                current = info;
                break;
            }
        }
        if (current == null) {
            //没有权限
            setFailedRequest(JSON.toJSONString(new TokenForbiddenResponse("Token Forbidden!")), 200);
//        } else{
//            //有权限,记录日志
//            PermissionInfo[] pms =  result.toArray(new PermissionInfo[]{});
//            PermissionInfo pm = pms[0];
//            if(!"GET".equals(method)){//将非GET请求记录下来
//                setCurrentUserInfoAndLog(ctx, user, pm);//写入日志
        } else {
            if (!RequestMethod.GET.toString().equals(current.getMethod())) {
            setCurrentUserInfoAndLog(ctx, user, current);
            }
        }
    }


    /**
     * URI是否以什么打头
     * @param requestUri
     * @return
     */
    private boolean isStartWith(String requestUri) {
        boolean flag = false;
        for (String s : startWith.split(",")) {
            if (requestUri.startsWith(s)) {
                return true;
            }
        }
        return flag;
    }

    /**
     * Reports an error message given a response body and code.
     * 网关抛异常
     * @param body
     * @param code
     */
    private void setFailedRequest(String body, int code) {
        log.debug("Reporting error ({}): {}", code, body);
        RequestContext ctx = RequestContext.getCurrentContext();
        ctx.setResponseStatusCode(code);
        if (ctx.getResponseBody() == null) {
            ctx.setResponseBody(body);
            ctx.setSendZuulResponse(false);
//            throw new RuntimeException("Code: " + code + ", " + body); //optional
        }
    }
}
